/**
 * @author Saint
 * GPS manage class to access location speed altitude distance
 */
package saint.manage;

import java.util.Iterator;
import java.util.List;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.location.*;
import android.os.Bundle;

public class GPSManage {
	//Location info
	private double speed;
	private double accuracy;
	private double longitude;
	private double latitude;
	private double altitude;
	private double distance;
	private Location lastLocation;
	
	// Signal and satellite info
	private int satelliteInUse;
	private int satelliteInView;
	private double snr;
	
	// Speed unit identify
	private int speedUnit;
	private int distantUint;
	
	// Location Provider (GPS or Network)
	private String providers;
	//private class
	private LocationManager locationManager;
	private Context classContext;
	
	//Interface class
	private LocationListener locationListener;
	private GpsStatus.Listener gpsStatusListener;
	private GPSListener gpsListener;
		
	// Private static member
	/**
	 * GPS minimum request to update represent on msec.
	 */
	private static final int minUpdateTime = 500;
	/**
	 * GPS minimum request to update represent on meter
	 */
	private static final int minDistance = 0;
	
	// Public static member
	public static final int GPS_STATUS_UPDATE = 0;
	public static final int GPS_LOCATION_UPDATE = 1;
	
	//============ Constructor ===============================================/
	/**
	 * Class constructor
	 * @param context Application context
	 */
	public GPSManage (Context context) {
		classContext = context;
		locationManager = (LocationManager) context
				.getSystemService(Context.LOCATION_SERVICE);
		locationListener = null;
		gpsListener = null;
		gpsStatusListener = null;
		lastLocation = null;

		speed = 0;
		accuracy = 0;
		longitude = 0;
		latitude = 0;
		altitude = 0;
		distance = 0;

		snr = 0;
		satelliteInUse = 0;
		satelliteInView = 0;

		speedUnit = Convert.KM_PER_HOUR;
		distantUint = Convert.KILOMETER;
	}
	
	//============ Public method =============================================/
	/**
	 * Start GPS to get information
	 */
	public void startGPS() {
		final Criteria criteria = new Criteria();

		if (locationManager == null) {
			locationManager = (LocationManager) classContext
					.getSystemService(Context.LOCATION_SERVICE);
		}

		criteria.setAccuracy(Criteria.ACCURACY_FINE);
		criteria.setSpeedRequired(true);
		criteria.setAltitudeRequired(true);
		criteria.setBearingRequired(false);
		criteria.setCostAllowed(true);
		criteria.setPowerRequirement(Criteria.POWER_LOW);

		// Get best Provider
		providers = locationManager.getBestProvider(criteria, true);

		// Get GPS status update
		 gpsStatusListener = new GpsStatusListenner();

		// Request location Update
		locationListener = new LocationCallback();
		if (providers != null && providers.length() > 0) {
			locationManager.requestLocationUpdates(providers, minUpdateTime,
					minDistance, locationListener);
		} else {
			final List<String> proviers = locationManager.getProviders(true);
			for (final String provider : proviers) {
				locationManager.requestLocationUpdates(provider, minUpdateTime,
						minDistance, locationListener);
			}
		}

	}
	
	/**
	 * Stop GPS
	 */
	public void stopGPS() {
		try {
			if (locationManager != null && locationListener != null) {
				locationManager.removeUpdates(locationListener);
			}
			if (gpsListener != null) {
				locationManager.removeGpsStatusListener(gpsStatusListener);
			}
		} catch (final Exception ex) {
		}
	}

	/**
	 * Check information of location 
	 * @return return null if not have information.
	 */
	public GPSInfo isHasGpsInfo() {
		Location location = locationManager
				.getLastKnownLocation(LocationManager.GPS_PROVIDER);
		getGpsLocationInfo(location);
		if ((accuracy != 0) || (latitude != 0) || (longitude != 0)
				|| (altitude != 0)) {
			return new GPSInfo(0, accuracy, longitude, latitude, altitude, 0);
		} else {
			return null;
		}

	}

	/**
	 * Check GPS enable if not enable will be ask for setting GPS. 
	 */
	public void checkGPSEnable() {
		boolean isGPSEnable;
		isGPSEnable = locationManager
				.isProviderEnabled(LocationManager.GPS_PROVIDER);

		if (!isGPSEnable) {
			AlertDialog.Builder alertBuilder = new AlertDialog.Builder(
					classContext)
					.setMessage("GPS is not enable")
					.setMessage(
							"GPS is not enable. Do you want to go settting?")
					.setPositiveButton("OK",
							new DialogInterface.OnClickListener() {

								@Override
								public void onClick(DialogInterface dialog,
										int which) {
									Intent settingIntent = new Intent(
											android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
									classContext.startActivity(settingIntent);

								}
							})
					.setNegativeButton("Cancel",
							new DialogInterface.OnClickListener() {

								@Override
								public void onClick(DialogInterface dialog,
										int which) {
									dialog.cancel();

								}
							});

			alertBuilder.show();
		}
	}

	/**
	 * Add GPS status listener to get GPS status information
	 */
	public void  addGPSStatusListenner() {
		locationManager.addGpsStatusListener(gpsStatusListener);
	}
	
	/**
	 * Get speed if not has speed will return 0
	 * @return Speed value
	 */
	public double getSpeed() {
		return speed;
	}

	/**
	 * Get accuracy if not has will return 0
	 * @return Accuracy value
	 */
	public double getAccuracy() {
		return accuracy;
	}

	/**
	 * Get longitude
	 * @return Longitude value
	 */
	public double getLongitude() {
		return longitude;
	}

	/**
	 * Get latitude
	 * @return Latitude value
	 */
	public double getLatitude() {
		return latitude;
	}

	/**
	 * Get Altitude
	 * @return Altitude value
	 */
	public double getAltitude() {
		return altitude;
	}

	/**
	 * Get distance
	 * @return Distance value
	 */
	public double getDistance() {
		return distance;
	}

	/**
	 * Get index of distance unit
	 * @return index of distance unit
	 * @see Convert class
	 */
	public int getDistanceUnit() {
		return distantUint;
	}

	/**
	 * Set distance unit 
	 * @param value index of distance unit
	 * @see Convert class
	 */
	public void setDistanceUnit(int value) {
		distantUint = value;
		distance = Convert.ConvertDistance(distance, distantUint);
	}
	
	/**
	 * Set distance 
	 * @param value Distance value
	 */
	public void setDistance(double value) {
		distance = value;
	}
	
	/**
	 * Get signal to noise ratio (Signal strength)
	 * @return SNR value
	 */
	public double getSNR() {
		return snr;
	}

	/**
	 * Get number of satellites in use
	 * @return Satellites number
	 */
	public int getSatelliteInUse() {
		return satelliteInUse;
	}

	/**
	 * Get number of satellites can get signal.
	 * @return Satellite number
	 */
	public int getSatelliteInView() {
		return satelliteInView;
	}

	/**
	 * Set listener to callback to activity
	 * @param listener pointer to class
	 */
	public void setGPSManagerListener(GPSListener listener) {
		this.gpsListener = listener;
	}

	/**
	 * Get speed unit ex. km/h mil/h..
	 * @return index of unit
	 * @see Convert class
	 */
	public int getSpeedUnit() {
		return speedUnit;
	}

	/**
	 * Set speed unit
	 * @param value index of speed unit 
	 * @see Convert class
	 */
	public void setSpeedUnit(int value) {
		speedUnit = value;
	}

	
	
	// ============ Private Method ============================================
	private void getGpsStatusInfo() {
		boolean isGps = providers.equals(LocationManager.GPS_PROVIDER);
		if (isGps) {

			GpsStatus gpsStatus = locationManager.getGpsStatus(null);
			GpsSatellite gpsSat;
			float snrRatio = 0;
			int satNumber = 0;
			int satUse = 0;
			if (gpsStatus != null) {
				Iterable<GpsSatellite> satellites = gpsStatus.getSatellites();
				Iterator<GpsSatellite> sat = satellites.iterator();

				while (sat.hasNext()) {
					gpsSat = sat.next();
					if (gpsSat.getSnr() != 0) {
						snrRatio += gpsSat.getSnr();
						satUse++;
					}
					satNumber++;
				}

				satNumber--;
				satelliteInView = satNumber;
				// Mean of SNR
				snrRatio = snrRatio / satUse;

				snr = snrRatio;

				satelliteInUse = satUse;
			}
		}
	}

	private void getGpsLocationInfo(Location location) {
		float _speed = 0;

		// Get Speed
		_speed = location.getSpeed();
		speed = Convert.ConvertSpeed(_speed, speedUnit);

		// Get Accuracy
		accuracy = location.getAccuracy();

		// Get Altitude
		altitude = location.getAltitude();

		// First location detect
		if (lastLocation == null) {
			lastLocation = location;
			distance = 0;
			speed = 0;
		} else {
			// Check last location has info
			if (_speed != 0) {
				double dis = 0;
				dis = lastLocation.distanceTo(location);
				if (dis > 1) {
					// Convert distance unit
					distance += Convert.ConvertDistance(dis, distantUint);
					// Get last location
					lastLocation = location;
				}
			}
		}
		// Get longitude
		longitude = location.getLongitude();
		// Get latitude
		latitude = location.getLatitude();

	}

	// ========== Private class implement =====================================
	/**
	 * Location callback to get information. This class use for internal process
	 *
	 */
	private class LocationCallback implements LocationListener {
		public void onLocationChanged(Location location) {
			getGpsLocationInfo(location);
			getGpsStatusInfo();
			// Callback to activity
			if (gpsListener != null) {
				gpsListener.onGPSUpdate(GPS_LOCATION_UPDATE,
						new GPSInfo(speed, accuracy, longitude, latitude,
								altitude, distance));
			}
		}

		public void onProviderDisabled(String provider) {

		}

		public void onProviderEnabled(String provider) {

		}

		public void onStatusChanged(String provider, int status, Bundle extras) {

		}

	}

	/**
	 * GPS Status callback
	 */
	private class GpsStatusListenner implements GpsStatus.Listener {
		public void onGpsStatusChanged(int event) {
			if (event == GpsStatus.GPS_EVENT_SATELLITE_STATUS) {
				getGpsStatusInfo();
				if (gpsListener != null) {
					// Callback to activity
					gpsListener.onGPSUpdate(GPS_STATUS_UPDATE, null);
				}
			}

		}
	}
}
